package spectra.ee.web.api.controller;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException;
import org.apache.commons.fileupload.FileUploadBase.FileUploadIOException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;

import spectra.base.BasePublic;
import spectra.base.licensemanager.Encrypt;
import spectra.base.log.LP;
import spectra.base.log.Logger;
import spectra.base.util.StringUtil;
import spectra.base.util.Util;
import spectra.ee.EEWebConfig;
import spectra.ee.web.WebConfig;
import spectra.ee.web.WebPublic;
import spectra.ee.web.api.ApiPublic;
import spectra.ee.web.api.model.FormFactory;
import spectra.ee.web.api.model.ParameterForm;
import spectra.ee.web.api.model.ResultData;
/**
 * Ajax 요청을 처리하기 위한 기반 클래스 <br/>
 * 다음과 같은 기능을 제공한다.<br/>
 * - request 로 부터 command, form, service 정보를 가져온다. (필수 파라메터)<br/>
 * - 나머지 파라메터를 form 객체에 담아서 서비스 객체를 호출한다.<br/>
 * - 서비스 함수를 호출하고 리턴값을 원하는 리턴형태(json)으로 변환한다.<br/>
 *
 * ex) *.mx?command=getFaqList&form=kb&service=kb&kbId=KB201001010001 <br/>
 */
/**
 * @author dhheo
 *
 */
public abstract class AjaxBaseServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	/** Logger객체. */
    private static Logger logger = Logger.getLoggerInstance(AjaxBaseServlet.class.getName());
    protected String resultURL = null;
    /** 사이트의 인증 키 */
    protected String AUTH_KEY = null;
    /** 파일 구분자. */
    protected static final String FILE_SEPERATOR = "/";      //무조건 /만 쓴다.
    /**
     * @see HttpServlet#HttpServlet()
     */
    public AjaxBaseServlet() 
    {
        super();
    }

    /**
     * 1. 서블릿 초기화시 필요한 작업
     * 
     * 맵핑 정보를 제공하는 클래스 생성
     * 각 설정 파일
     */
    public void init() throws ServletException 
    {
    	//서블릿 파라메터값을 따로 담는다.
    	Enumeration e = getServletConfig().getInitParameterNames();
    	Map initParameter = new HashMap();
    	while (e.hasMoreElements())
    	{
    		String name = (String) e.nextElement();
    		String value = getServletConfig().getInitParameter(name);
    		initParameter.put(name, value);
    		logger.debug("[servlet config] " + name + " : " + value);
    	}
    	init(initParameter);
    	//클라이언트 인증을 위해 t_config_property_의 URL_WEBAPPS 를 암호화 한 값과 비교한다.
    	
    	this.AUTH_KEY = Encrypt.crypt(WebConfig.getConfigValue("COMMON", "URL_WEBAPPS"), EEWebConfig.getEncryptKey(), BasePublic.ENCODING);
    	//\n\r가 들어가는 경우가 있어서, 혹시 \n\r 코드가 있으면 지웁니다.
    	this.AUTH_KEY = this.AUTH_KEY.replaceAll("\r\n", "");
    	this.AUTH_KEY = this.AUTH_KEY.length() > 32 ? this.AUTH_KEY.substring(0, 32) : this.AUTH_KEY;
    	
    	logger.debug("auth key : " + AUTH_KEY + ", length: " + AUTH_KEY.length());
    }
    
    public abstract void init(Map initParameter);
    
    /**
     * 요청이 들어오면, request 로 부터 command 객체를 생성하고 인증 처리한다.
     */
    protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException
	{
    	//결과 리턴 형식은 달라질수 있으므로 jsp 로 포워딩 하여 구현한다.
		RequestDispatcher rd = getServletContext().getRequestDispatcher(getView(request.getServletPath()));

    	try
		{
    		//파라메터를 Map 으로 변경한다.
    		Map paramMap = new HashMap();
    		
    		// request 가  multipart 형식으로 들어오는지 확인한다.
    		// multipart 형식인 경우 첨부파일이 들어오지 않으면 일반 파라메터 처리 하고, 첨부파일이 들어오면 일단 파일 저장하고 파라메터 에 
    		// attachList 에 파일 정보를 담아서 리턴한다.
    		boolean isMultipart = ServletFileUpload.isMultipartContent(request);
    		
    		if (isMultipart)
    		{
    			paramMap = getMutipartParameter(request);
    		}
    		else
    		{
        		Enumeration paramNames = request.getParameterNames();
        		
        		while (paramNames.hasMoreElements())
        		{
        			String name = paramNames.nextElement().toString(); 
        			String value = request.getParameter(name); 
        			logger.debug("[PARAM] " + name + ":" + value);

        			paramMap.put(name, value);
        		}
    		}
    		
    		
    		//파라메터의 메시지 타입에 폼 객체 생성한다.
    		ParameterForm parameterForm = FormFactory.create(paramMap);
    		
    		if (isMultipart)
    		{
    			// multipart 형식인 경우 첨부파일 정보를 리턴한다.
    			ArrayList list = new ArrayList();
    			list.add(paramMap);
    			ResultData result = new ResultData(ApiPublic.ERROR_CODE_SUCCESS, "", list);
        		request.setAttribute(ApiPublic.RESPONSE_OBJECT, result);
    		}
    		else if ("deleteAttach".equals(parameterForm.getCommand()))
    		{
    			paramMap = getDeleteAttachParameter(parameterForm);
    			
    			// command="deleteAttach"(첨부파일 삭제)인 경우 첨부파일 삭제 후, 그 결과를 리턴한다.
    			ArrayList list = new ArrayList();
    			list.add(paramMap);
    			ResultData result = new ResultData(ApiPublic.ERROR_CODE_SUCCESS, "", list);
        		request.setAttribute(ApiPublic.RESPONSE_OBJECT, result);
    		}
    		else if (validate(parameterForm))
    		{
        		Object objRet = invokeService(parameterForm);
        		ResultData result = new ResultData(ApiPublic.ERROR_CODE_SUCCESS, "", objRet);
        		request.setAttribute(ApiPublic.RESPONSE_OBJECT, result);
    		}
    		else
    		{
    			//파라메터 validation 에러
    			ResultData result = new ResultData(ApiPublic.ERROR_CODE_INVALID_PARAMETER, "invalid parameter", null);
    			request.setAttribute(ApiPublic.RESPONSE_OBJECT, result);
    		}
    		//printResult(response, objRet);
		} 
    	catch (Exception e)
		{
			logger.error(e);
			e.printStackTrace();
			ResultData result = new ResultData(ApiPublic.ERROR_CODE_EXCEPTION, e.toString(), null);
			request.setAttribute(ApiPublic.RESPONSE_OBJECT, result);
		}
    	rd.forward(request, response);
	}
    
    /**
     * Multipart 요청의 경우는 파일을 저장 처리하고 나머지 파라메터는 리턴한다.
     * 현재까지 파일 업로드가 필요한 서비스는 QnA 밖에 없다. 
     * QnA 서비스로 고려하고 QnA 서비스가 아니면서 파일 업로드 시도는 차단한다.
     * @param request
     * @return
     */
    private Map getMutipartParameter(HttpServletRequest request)
	{
		//필요한 환경설정 값을 가져온다.
    	String strAttachDir = WebConfig.getEnvValue(WebPublic.COM_ATTACH_DIR); // 도메인의 첨부파일 경로

        // 해당 도메인의 첨부파일 경로가 이상할 경우..
        if( StringUtil.isBlank(strAttachDir) )
        {
            strAttachDir = WebConfig.getConfigValue(WebPublic.CFG_SVC_COMMON, WebPublic.DIR_DATAFILE_HOME); // 첨부파일 업로드 경로
        }
                
        //QnA 서비스로 간주한다.
        String strMaxUploadFileSize = WebConfig.getEnvValue(WebPublic.QNA_CUSTOMER_ATTACH_MAX_SIZE); // 첨부파일 최대 사이즈
        long lMaxUploadFileSize = Long.parseLong(strMaxUploadFileSize) * 1024 * 1024;
        
        // 파일명으로 사용할 GUID
        String strGuid    = Util.getGuid();
        strGuid = strGuid.replace(':','_');
        strGuid = strGuid.replace('-','_');
        
        // 현재 날짜의 디렉토리에 첨부파일을 업로드 한다.
        String strFilePath = "/qna/inbound" + Util.getDateDir();
        String strFileUploadPath = strAttachDir + strFilePath;
        
        String domainId = null;
		String strFileName = null;
		String saveFileName = null;
		String saveFileExt = null;
		String attachPath = null;
		
		long lFileSize = -1;
		
		/*
		 * 첨부파일 업로드 결과
		 * 	- 성공 : 1
		 * 	- 실패 : -1
		 * 	- 용량 초과 : -2
		 */
		int iResultValue = 0;
		
		Map mapResult = new HashMap();
        try
        {
	        ServletFileUpload upload = new ServletFileUpload();
			upload.setHeaderEncoding("utf-8");
			FileItemIterator iter = upload.getItemIterator(request);		
			
			while (iter.hasNext())
			{
				FileItemStream item = iter.next();
				String name = item.getFieldName();
				InputStream stream = item.openStream();
				if (item.isFormField())
				{
					mapResult.put(name, Streams.asString(stream, "utf-8"));
					if (name.equals(ApiPublic.PARAM_DOMAIN_ID))
					{
						domainId = name;
					}
					upload.setFileSizeMax(lMaxUploadFileSize);
				}
	            else
	            {
	            	// 실제 파일 이름
	                strFileName = item.getName();
	                strFileName = strFileName.substring(strFileName.lastIndexOf("/") + 1);
	                strFileName = strFileName.substring(strFileName.lastIndexOf("\\") + 1);
	
	                // 저장할 파일 이름
	                saveFileExt = strFileName.substring(strFileName.lastIndexOf(".") + 1);
	                saveFileName = strGuid + "." + saveFileExt;
	                
	                // 확장자가 없을 경우, 패스
	                if("".equals(saveFileExt)) continue;
	                //domainId;
	                
	                if (logger.isDebugEnabled())
	                {
	                	logger.debug(LP.NOTE, "fileUploadPath:" + strFileUploadPath + ", saveFileName:" + saveFileName);
	                }
	                
	                File dirPath = new File(strFileUploadPath);
	                if (!dirPath.exists())
	                {
	                    dirPath.mkdirs();
	                }
	
	                File uploadedFile = new File(strFileUploadPath + FILE_SEPERATOR + saveFileName);
	                lFileSize = Streams.copy(item.openStream(), new BufferedOutputStream(new FileOutputStream(uploadedFile)),true);
	                
	                //업로드 성공하면, map 에 파일 정보를 담는다.
	                mapResult.put(ApiPublic.FIELD_FILEPATH, strFilePath + FILE_SEPERATOR + saveFileName);
	                mapResult.put(ApiPublic.FIELD_FILENAME, strFileName);
	                mapResult.put(ApiPublic.FIELD_FILEEXT, strFileName.substring(strFileName.lastIndexOf(".") + 1));
	                mapResult.put(ApiPublic.FIELD_FILESIZE, lFileSize);
	                
	                iResultValue = 1;
	            }
	        }
		}
	    catch (FileUploadIOException e)
	    {
	        if( e.getCause() instanceof FileSizeLimitExceededException )
	        {
	        	iResultValue = -2;
	            logger.warn(LP.NOTE, "Failed to upload by limit MaxSize : " + lMaxUploadFileSize);
	        }
	        else
	        {
	        	iResultValue = -1;
	            logger.error(LP.EXCEPTION, e);
	        }
	    }
	    catch (IOException e)
	    {
	    	iResultValue = -1;
	    	logger.error(LP.EXCEPTION, e);	    	
	    }
		catch (Exception e)
		{
			iResultValue = -1;
			logger.error(LP.EXCEPTION, e);			
		}
		
		mapResult.put(ApiPublic.FIELD_FILE_RESULTVALUE, iResultValue);
		
		return mapResult;
	}
    
    /**
     * 첨부파일 삭제요청일 경우, 삭제 처리하고 나머지 결과를 파라미터로 리턴한다.
     * @param request
     * @return
     */
    private Map getDeleteAttachParameter(ParameterForm parameterForm)
	{
    	//필요한 환경설정 값을 가져온다.
    	String strAttachDir = WebConfig.getEnvValue(WebPublic.COM_ATTACH_DIR); // 도메인의 첨부파일 경로
    	
    	// 해당 도메인의 첨부파일 경로가 이상할 경우..
        if( StringUtil.isBlank(strAttachDir) )
        {
            strAttachDir = WebConfig.getConfigValue(WebPublic.CFG_SVC_COMMON, WebPublic.DIR_DATAFILE_HOME); // 첨부파일 업로드 경로
        }
        
        Map paramMap = parameterForm.getParameterMap();
        String strAttachFullPath = (String)paramMap.get("attachFullPath");
        if(strAttachFullPath != null)
        {
        	strAttachFullPath = strAttachDir + strAttachFullPath.substring(0, strAttachFullPath.indexOf("|"));
        }
        
        /*
		 * 첨부파일 삭제 결과
		 * 	- 성공 : 1
		 * 	- 실패 : -1
		 */
        int iResultValue = 0;
        
    	Map mapResult = new HashMap();
    	try
    	{
    		File file = new File(strAttachFullPath);
    		if (file.isFile())
    		{
    			// 파일이 존재하면 삭제
    	        file.delete();
    	        
    	        // 삭제에 성공하면, map 에 파일 정보를 담는다.
    	        iResultValue = 1;
    		}
    	}
    	catch(Exception e)
    	{
    		iResultValue = -1;
    		logger.error(LP.EXCEPTION, e);
    	}
    	
    	mapResult.put(ApiPublic.FIELD_FILE_RESULTVALUE, iResultValue);
    	
    	return mapResult;
	}

	/**
     * Business 로직단을 수행한다.
     * 
     * @param ajaxFrom
     * @return
     * @throws Exception
     */
    public abstract Object invokeService(ParameterForm parameterForm) throws Exception; 
    
    /**
     * View 정보를 가져옵니다. View 정보를 가져오는 방법은 하위 클래스에서 정의합니다.
     * 
     * @param urlPath
     * @return
     */
    public abstract String getView(String urlPath);
    
    public boolean validate(ParameterForm parameterForm)
    {
    	boolean isSuccess = true;
		//필수적인 파라메터가 값이 없으면 오류
		if (	//StringUtil.isEmpty(parameterForm.getAuthKey()) || 
				StringUtil.isEmpty(parameterForm.getServiceName())||
				StringUtil.isEmpty(parameterForm.getCommand()))
		{
			return false;
		}
		//authId 가 맞는지 확인
		//isSuccess = parameterForm.getAuthKey().equals(AUTH_KEY);
		
		return isSuccess;
    }
	/*
	protected void doGet(HttpServletRequest request, HttpServletResponse response) 
			throws ServletException, IOException 
	{
		System.out.println("doGet : ");
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) 
			throws ServletException, IOException 
	{
		System.out.println("doPost : ");
	}
	*/
}
